Skip to content

WoT Binding and WoT Connectivity 1.1 - #4093

Closed
marcschier wants to merge 113 commits into
masterfrom
marcschier/wot-binding-xregistry
Closed

WoT Binding and WoT Connectivity 1.1#4093
marcschier wants to merge 113 commits into
masterfrom
marcschier/wot-binding-xregistry

Conversation

@marcschier

@marcschier marcschier commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Status and dependencies

Draft proof implementation.

What this proves

Lossless NodeSet2 ↔ WoT conversion

  • Adds a dependency-light, AOT-safe WoT lexical model and converters to Opc.Ua.Types.
  • Preserves unrecognized/unmapped JSON-LD values as digest-protected RFC 6901 residue entries in standard NodeSet Extensions.
  • Emits the deterministic, versioned uav:nodes projection only when readable-only reverse conversion does not reproduce the complete UANodeSet.
  • Supports WhenRequired, Always, and Never preservation modes. uav:nodeSet is emitted only for explicit byte archival or a demonstrated fallback, and completeness tests use Never.
  • Reconstructs from the complete native projection, an explicit envelope, or ordinary binding-authored TD/TM documents.
  • Includes bounded resolver contracts, structured diagnostics and canonical NodeSet comparison.

WoT source generation

  • Accepts .tm.json, .td.json, .tm.jsonld, .td.jsonld and explicitly opted-in .jsonld AdditionalFiles.
  • Converts them to an in-memory UANodeSet and uses the existing direct NodeSet code-generation pipeline.
  • Adds stable MODELGEN030MODELGEN034 diagnostics for parsing, conversion and virtual-path collisions.

Live NodeManager shadow reload

  • Extends [Server] Add live NodeManager lifecycle support #4015 with ShadowReloadAsync.
  • Adds ImmediateReloadAsync as the alternative policy: it atomically routes new requests to the replacement, invalidates affected non-durable monitored items with BadNodeIdUnknown, detaches disposed owner graphs, and rejects durable subscriptions before commit.
  • Routes new service requests to the replacement generation while existing monitored items remain on the retired generation.
  • Disposes retired generations after subscriptions drain.
  • Keeps the existing fail-closed ReloadAsync/RemoveAsync behavior unchanged.

Registry-first WoT Connectivity 1.1

  • Compiles the pinned xRegistry model and the single combined WoT-Con 1.1 NodeSet. The combined model contains every published 1.02 node plus the additive registry model in the same namespace, so the old ModelDesign is not generated a second time.
  • Adds a stable, injectable TD/TM registry with in-memory and atomic file-backed stores, versions, epochs, desired/active state, persisted labels and bounds.
  • Exposes browseable xRegistry/WoT-Con 1.1 group/resource/file nodes, FileTransfer, CRUD, label, validation, enable/default-version and detailed Refresh methods.
  • Builds TM/TD dependency closures and materializes them through runtime NodeSet lifecycle Add/ShadowReload/Remove.
  • Resolves document dependencies only from the registry snapshot (or an injected federation layer); arbitrary external document URLs are not fetched.
  • Exposes WotProjectionRetirementPolicy so deployments select graceful or immediate replacement.
  • Retains invalid versions and the previous active projection, emits resource/refresh events, and reuses lifecycle model-change notifications.
  • Adapts the existing v1.02 CreateAsset/WoTFile/CloseAndUpdate workflow to the registry without changing published signatures or clients.

Replaceable protocol binding code-behind

Adds a dependency-light planner/binder project for:

  • HTTP
  • CoAP
  • MQTT
  • Modbus TCP
  • BACnet
  • PROFINET
  • LoRaWAN
  • OPC UA

Concrete optional executor projects cover HTTP, MQTT, Modbus TCP and OPC UA. The OPC UA executor supports Read, Write, native data-change observation, Method invocation and Event subscription, including portable nsu= NodeIds.

BACnet, PROFINET, LoRaWAN and CoAP are planner/validation-only in this proof and report their non-executable capability explicitly.

Review remediation

The follow-up review fixes are included:

  • shadow-retired data monitored items remain modifiable, transferable and deletable through their owning NodeManager and trigger prompt retired-generation cleanup;
  • mqtts enables TLS, defaults to 8883, resolves credentials/trust and fails closed;
  • Modbus validates the full 16-bit address range and maps function-only forms exactly;
  • HTTP redirects are explicit, bounded and cannot forward credentials across origins;
  • registry mutations become visible only after an atomic durable store commit;
  • all generated typed WoT event fields are populated;
  • uav:nodeSet.sha256 is mandatory and malformed XML becomes structured diagnostics;
  • one resolver context enforces aggregate limits across the full conversion;
  • event affordances carry uav:eventType, persisted identities use portable nsu= ExpandedNodeIds, and HasComponent subtypes round-trip exactly;
  • generated contexts bind ua and deterministic nsN prefixes; ReferenceType model names are emitted directly in link rel, with uav:refId as definitive identifier, and name/NodeId conflicts are rejected;
  • compact mapToTypeName / congruentTypeName hints remain paired with definitive identifiers, while arbitrary instance targets remain ExpandedNodeIds;
  • the non-published uav:typedReference / refTypeName draft surface is removed;
  • readable BrowseNames/paths prefer @context prefixes (with URI-qualified QualifiedNames also accepted), authored IDs are retained during synthesis, and the proof omits uav:nodes when the semantic mapping alone is equivalent;
  • native conversion covers root/model/alias/node fields, all eight NodeClasses, values, DataType definitions, permissions, translations and XML extensions without an envelope;
  • unknown JSON-LD members survive repeated WoT ↔ NodeSet cycles through bounded residue with stable context/link/affordance selectors;
  • observable / observeproperty is documented as advertised binding support, not a restriction on core UA monitorability.
  • registry FileType write-open/write/write-position/commit paths enforce the management SecureChannel policy while read-only access remains deployment-selectable;
  • committed reload cleanup failures carry the authoritative replacement registration so the coordinator cannot retain a stale handle, and committed model/namespace notifications are still attempted;
  • immediate retirement is serialized with monitored-item and durable-subscription mutations, emits one terminal status for Reporting/Sampling data items, clears event/aggregate queues, suppresses resend, and releases old owner graphs.

Compatibility

  • Existing OPC 10100-1 v1.02 model/NodeIds/method signatures remain unchanged.
  • Existing IWotAssetProviderFactory integrations remain supported.
  • Concrete transport dependencies are isolated from Opc.Ua.WotCon.Server.
  • Planner/core projects retain the repository's legacy target-framework matrix; concrete executors target modern .NET.

Commit structure

  1. Add shadow NodeManager reload support
  2. Add lossless WoT conversion and source generation
  3. Add injectable WoT protocol bindings
  4. Add registry-first WoT Connectivity runtime
  5. Align WoT Connectivity proof to combined 1.1 spec model
  6. Align WoT converter with portable Binding identity
  7. Make WoT NodeSet conversion native-first
  8. Address WoT model concept review feedback
  9. Address WoT mapping review feedback
  10. Prefer context prefixes for WoT QualifiedNames
  11. Implement selectable WoT projection retirement
  12. Consolidate WoT bindings and add WoT-driven aggregation sample
  13. Apply code style and analyzer fixes to WoT bindings branch
  14. Wire the DI NodeManager fluent builder DataType resolver
  15. Address PR review feedback on WoT docs and sample layout
  16. Raise PR patch coverage from 70% to 91% with unit and integration tests
  17. Fix net48 test failures and remove dead WoT mapping code
  18. Merge copilot/3993-live-node-manager-lifecycle into marcschier/wot-binding-xregistry
  19. Adopt the merged xRegistry implementation and packages
  20. Derive WotRegistryClient from the shared XRegistryClient
  21. Make subscription transfer effects transactional
  22. Add a WoT blob store implementing the xRegistry resource store contract
  23. Let a WoT registry keep its document bytes in an injected resource store
  24. Add WoT materialization and polling extension points (#4108, #4109, #4110)
  25. Address code review findings and PR feedback

Validation

  • full Opc.Ua.Types.Tests net10.0 and net48: passed
  • source-generation suite, including all WoT AdditionalFile cases: passed
  • full Opc.Ua.WotCon.Tests and Opc.Ua.WotCon.Bindings.Tests net10.0: passed
  • lifecycle-focused net10.0 and net48 suites: passed
  • registry transaction and typed-event integration suites: passed
  • full dotnet build UA.slnx -c Release: passed
  • NativeAOT win-x64 publish/run: 115 passed
  • full converter/source-generator/legacy-TFM validation completed before the final rebase
  • pinned xRegistry/WoT-Con 1.1 models match the specification repository
  • immediate/graceful lifecycle, registry FileType security and committed-reload recovery tests: passed
  • WoT materialization policy tests: 22 passed on net10.0 and net48
  • affected Opc.Ua.Server / Opc.Ua.WotCon.Server builds on net10.0 and net472: passed with 0 warnings
  • git diff --check: clean

Documentation layout

  • docs/WoTProtocolBindings.md + docs/WoTBindingDevelopment.md are merged into a single docs/WotBindings.md, ordered as Part 1 (the bindings that ship today) and Part 2 (adding your own binding).
  • docs/WoTAggregationSample.md moved to samples/WotCon/README.md, next to the WoT samples it documents.
  • The runtime-loaded WoT documents live in samples/WotCon/AggregationClient/Documents: the client is what uploads them into the server, and the aggregation server has no dependency on them.
  • The aggregation guide now states that the registry accepts documents in any order, that an incomplete dependency closure is never partially published, and how progress surfaces under AutoRefresh = true versus the sample's AutoRefresh = false.

Test coverage

Codecov reported 70.39 % patch coverage against the 80 % codecov/patch gate. Tests were added across Opc.Ua.Types/Wot, Opc.Ua.WotCon.Server, Opc.Ua.WotCon.Bindings*, Opc.Ua.Server and the WoT source generator, raising measured patch coverage to 91.2 % (10 750 of 11 782 changed lines). No production behaviour was changed to achieve it; the only production edit was removing the unreachable WotNodeSetConverter.ToCompactModelName helper.

Suite totals on net10.0, all warning-free: Opc.Ua.Types.Tests 8 390, Opc.Ua.WotCon.Tests 823, Opc.Ua.WotCon.Bindings.Tests 464, Opc.Ua.SourceGeneration.Tests 94, Opc.Ua.Server.Tests 3 851. On net48: Opc.Ua.Types.Tests 8 383 and Opc.Ua.WotCon.Tests 823.

Base branch merge

The live NodeManager lifecycle work (#4094) has landed in master and has been merged in; this PR is now based on master. The upstream NodeManager lifecycle interface consolidation was adopted (IMonitoredItemLifecycleIDetachableMonitoredItem, INodeManagerMonitoredItemTracker folded into ISubscriptionMonitoredItemLifecycle, ExecuteMonitoredItemMutationAsync moved to IDynamicNodeManagerHost) while retaining this branch's shadow/immediate reload support; INodeManagerMonitoredItemRetirementTracker now lives in its own file to match the new one-interface-per-file layout.

Reported issues addressed

Raised by @barnstee while evaluating whether UA Edge Translator can replace its hand-written WoT-Connectivity node manager with this library. All four target the deprecated OPC 10100-1 v1.02 asset surface; the answers live in the registry-first materialization runtime and Opc.Ua.WotCon.Bindings that this PR adds.

Review-round remediation (commit 3ea717089)

An implementation review raised 11 findings (5 High, 6 Medium) and 12 review threads were open. All are addressed.

Correctness / robustness

  • Content-addressed blobs are verified and skipped rather than rewritten on every commit, and blob writes stage to a temp file and publish through an atomic replace, so an interrupted write can no longer leave a partial blob that the unchanged manifest still references.
  • Added an IAtomicFileReplace capability implemented by LocalFileSystem and VirtualFileSystem, rather than a new IFileSystem member: that would break external implementers and is not expressible as a default interface member on netstandard2.0. Blob storage now fails fast instead of silently degrading to a destructive write.
  • The registry accessor borrows the container-wide ManagedSession instead of disposing it, so a failed ForServerAsync no longer closes the session other client facades are still using.
  • ModbusTcpClient reconnects a faulted connection on the next transaction; backoff stays in PollingWotSubscription so there is only one retry loop.
  • NodeSetComparer enforces MaxXmlDepth, which was validated but never read, so deeply nested XML reports a diagnostic instead of overflowing the stack.
  • WotMaterializationCoordinator enforces MaxResolverDocumentBytes, and decides namespace knownness from the live materialized closure because namespace-table entries outlive the NodeManagers that registered them.
  • Bulk load validates the group kind on cache hits as well as misses; GetOrCreateGroup normalizes names the way the registry service does; the resource output map is read under its gate.

API

  • Removed System.Nullable wrappers around INullable types across the WoT and registry surface, including TrySelectProjectionRoot.
  • The WoT resolver contracts are now async. The converter pre-resolves through a shared reference enumeration into a WotThingCatalog, and the synchronous core only performs lookups — so the Roslyn source generator stays synchronous and no sync-over-async is introduced.
  • Completed ExtensionObject.TryGetValue to decode through the codecs and reduced the Variant structure helpers to delegation, since the context-taking API already existed.
  • NodeSetComparer.CompareXml takes ReadOnlySpan<byte> instead of byte[].

Layout

  • WotCon samples grouped under samples/WotCon/ with the Wot prefix dropped from folder, project, assembly and namespace names.
  • NodeSetComparer moved next to the other NodeSet code in Opc.Ua.Export.
  • MemoryWotBinding moved out of the shipping bindings library into the aggregation sample and the test project.
  • Removed Sync-WotConModels.ps1 (belongs in the drafts repository); renamed the pinned NodeSet2 files to WotCon casing; reverted a blank-line-only change in Session.cs.

Server lifecycle

  • Request admission and lifecycle-waiter tracking are factored out of RequestManager into an optional registerable extension. With no extension registered the behaviour is exactly master's. The AsyncLocal scope nesting stays in RequestManager: it fixes scope propagation for the pre-existing scopes rather than adding a feature.
  • StandardServer disposal is documented and idempotent.
  • The Subscription/Session and server-shutdown threads asked for investigation and proposals before changes, so those are analysed and answered on the threads rather than rewritten here.

Validation: full UA.slnx builds clean on net10.0 (0 warnings) and net48 (0 errors). Suite totals on net10.0: Opc.Ua.Types.Tests 8 408, Opc.Ua.Server.Tests 3 978, Opc.Ua.WotCon.Tests 920, Opc.Ua.WotCon.Bindings.Tests 512, Opc.Ua.SourceGeneration.Core.Tests 3 743, Opc.Ua.SourceGeneration.Tests 112, Opc.Ua.XRegistry.Tests 171, Opc.Ua.Di.Tests 310, Opc.Ua.ISA95.Tests 137.

Server shutdown hardening (commit 46e5e5f2d)

Follow-up to the StandardServer.cs review thread, which asked for a thorough review of the server lifecycle handling with proposals before merge. The structural proposals remain open on that thread and no refactor was done; what landed here are the concrete defects that are wrong under any of those proposals:

  • The shutdown task slot is reserved under m_shutdownCoordinationLock while ShutdownServerInternalAsync now starts outside it. Previously every statement before its first await executed under the lock. ActiveTask is still published inside the lock, so concurrent callers keep joining a single shutdown.
  • The deferred retry is bounded by a maximum retry count and an overall time budget; it previously retried indefinitely as long as cleanup progress kept increasing.
  • Terminal shutdown failures surface through ServerError and a source-generated log instead of only being recorded on the shutdown state.
  • The deferred observer routes its exceptions rather than leaving them unobserved.

Final node-manager cleanup deliberately keeps CancellationToken.None: cancelling it would leak retired lifecycle state, node managers and the server semaphore after request admission has already been closed permanently. Those call sites are now commented to make the intent explicit.

Opc.Ua.Server.Tests: 3 980 passing, 0 warnings.

Open review threads

Three threads are intentionally left unresolved because they ask for a joint decision rather than a specific change, and each has a full written analysis posted:

  • StandardServer.cs — three ranked structural proposals for the shutdown coordinator, awaiting a direction.
  • Subscription.cs — a four-capability inventory of what this PR adds versus what master already supports, with a keep/refactor/revert recommendation per capability.
  • IMonitoredItem.cs — answered: immediate generation retirement is the one scenario master's detach/park/recover lifecycle cannot express.

@marcschier marcschier changed the title Draft: prove registry-first WoT Binding and Connectivity 1.1 stack DRAFT: WoT Binding and WoT Connectivity 1.1 Jul 25, 2026
marcschier and others added 16 commits July 25, 2026 08:48
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 29378426-2a98-41ee-9a6d-050af5b1141f
…ver limits

- Make uav:nodeSet.sha256 mandatory: a missing, malformed, or
  mismatched digest now produces a structured InvalidDigest/
  DigestMismatch diagnostic and no NodeSet is returned, instead of
  silently skipping integrity verification when the field was absent.
- Catch XML/deserialization failures from UANodeSet.Read and report
  them as a WotDiagnosticCode.MalformedNodeSet diagnostic instead of
  letting the exception escape the converter.
- Create exactly one WotResolutionContext per top-level conversion in
  WotNodeSetConverter.ToNodeSetCore, seeded from the converter options
  (depth, max documents, cumulative bytes), and thread that single
  context through link/thing resolution. Removes the previous
  per-link context ??= new WotResolutionContext() fallback in
  TryResolveTargetNodeId, which reset cycle/limit tracking for every
  link instead of applying bounds across the whole conversion.
- Seed WotNodeSetDocumentConverter's resolution context from its
  configured options instead of resolver defaults.
- Add WotNodeSetConverterOptions.MaxResolverDocuments/
  MaxResolverDocumentBytes/MaxResolverTotalBytes (aligned with
  WotResolverOptions defaults) plus ToResolverOptions() and
  validation, so aggregate resolver limits are configurable through
  the same options object used for the rest of the conversion.
- Add tests: missing/malformed digest, digest mismatch, malformed
  NodeSet XML without throwing, unsupported vs. accepted (base64)
  encoding, multi-link aggregate document/byte limits sharing one
  context, and sibling-link cycle detection that doesn't block
  unrelated links.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After a ShadowReload the retired generation is removed from the routing
table but still owns the monitored items created on it. Modify, Delete,
SetMonitoringMode and Transfer previously iterated only the visible
routing-table managers, so a same-namespace replacement generation would
claim (and fail on) the retired items - returning BadMonitoredItemIdInvalid
and leaving the retired generation's items undrained and its address space
never disposed until another lifecycle operation or shutdown.

Dispatch each data monitored item to its recorded owning NodeManager
(grouped by owner, each owner offered only its own indices) so a retired
generation services its own items and a same-namespace replacement cannot
cross-claim them. Event/all-event handling and result indexing are
unchanged.

Trigger prompt retired-generation cleanup when the last item drains: the
master notifies the lifecycle from the Delete path when an owner is no
longer registered, and the lifecycle runs cleanup on a background task
(request execution context suppressed, coordinated via the lifecycle
semaphore) so nothing is disposed inside the request callback. Existing
fail-closed Reload/Remove behaviour and the deferred-cleanup fallback are
preserved.

Add tests proving that after ShadowReload old data items can be modified,
disabled/re-enabled, transferred and deleted on the retired generation,
new reads use the replacement, old notifications keep flowing, and the
retired generation is disposed after the final drain without another
lifecycle operation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rects)

MQTT: mqtts now enables TLS and defaults to port 8883, resolving username /
password, TLS client certificate and trust anchors through the credential
provider. A declared security scheme fails closed when unresolved, and
username / password never downgrades to plaintext unless explicitly allowed.
plain mqtt stays explicit (port 1883).

Modbus: the planner enforces address 0-65535 and the address+quantity-1 range,
maps function-only forms exactly onto codes 1,2,3,4,5,6,15,16 (mnemonic or
numeric) and rejects op/function and entity/function mismatches. The executor
re-validates the range before the ushort/byte casts.

HTTP: the executor-owned HttpClient disables automatic redirects and applies a
bounded, origin-aware redirect policy that strips custom header/query
credentials across origins, refuses loops, non-http(s) schemes and https->http
downgrades, and caps hops. A caller-supplied client with a credential-bearing
form fails closed unless CallerClientHandlesRedirectSafety is set.

Adds planner, executor and E2E regression tests and updates the binding docs
and options. WotCredential gains runtime-only client certificate / trust
material.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Registry mutations now persist through a transactional store commit
before publishing the snapshot or raising Changed, and the generated
WoT V2 event types are fully populated from the coordinator arguments.

- IWotRegistryStore: replace the per-entity Upsert/Remove methods with a
  single atomic CommitAsync(snapshot). LoadAsync returns only the
  committed generation.
- FileWotRegistryStore: stage content-addressed version blobs durably,
  then switch a single manifest.json pointer with an atomic replace, so a
  crash never exposes a half-written generation; Load reads only the
  committed manifest.
- InMemoryWotRegistryStore: keep the committed snapshot behind an atomic
  reference switch (same failure semantics).
- WotRegistryService: commit durably before publishing m_snapshot or
  raising Changed for every CRUD/default/enabled/labels and projection
  path. On failure Current stays previous, no event is raised, a retry
  re-attempts persistence and a restart never sees partial data.
- WotRegistryNodeManager: populate every generated WoT event field
  (resource identity/kind, generation, phase/outcome, validation outcome,
  load state/failed node/reason, binding uri, refresh summary/request id)
  from WotMaterializationEventArgs via SetChildValue.
- Tests: fault-injection transaction tests (atomicity, no-event, retry,
  restart) and real EventFilter integration tests that verify the typed
  fields are delivered through the running server notifier chain.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Regenerate the Opc.Ua.WotCon model from the single revised
Opc.Ua.WoTCon NodeSet2, which incorporates the published OPC 10100-1
v1.02 model (NodeIds 1..172, deprecated) plus the additive registry
nodes (64000+) in one namespace (http://opcfoundation.org/UA/WoT-Con/,
version 1.1.0), replacing the separate 1.02 ModelDesign and the
Opc.Ua.WotCon.V2 companion model.

Model / build:
- Pin Opc.Ua.WoTCon.NodeSet2.xml/.csv (+ xRegistry) and drop the
  V2-named artifacts; update Sync-WotConModels.ps1 mappings/docs.
- Opc.Ua.WotCon.csproj: stop compiling WotConnection.xml/csv; compile
  the combined NodeSet once as Opc.Ua.WotCon (name WotCon, model URI
  WoT-Con/). Legacy 1.02 sources kept under Design/ for reference only.

Server / tests / docs:
- Replace all Opc.Ua.WotCon.V2 namespace/type references with the
  combined Opc.Ua.WotCon generated types (AddOpcUaWotCon, ObjectIds,
  BrowseNames, DataTypeIds, ...); remove user-visible V2 wording and
  rename the registry config section to OpcUa:WotConRegistry:Server.
- Decide model-node ownership cleanly via WotConModelPartition: the
  registry NodeManager owns {WotCon, xRegistry} and loads the registry
  slice (>=64000); the legacy asset NodeManager loads the incorporated
  1.02 slice (<64000). Neither claims the same static node twice.
- Add CombinedModelPreservationTests proving exact 1.02 NodeIds, typed
  method state/results and the generated client API are preserved, and
  that the registry types now coexist in Opc.Ua.WotCon.

Source generator (NodeSet2 -> ModelDesign) fixes exposed by generating
the richer combined model with typed method arguments:
- Map placeholder BrowseNames "<Name>" to "Name_Placeholder".
- Reuse an existing explicit method-type declaration instead of
  synthesizing a colliding duplicate, and carry the concrete method's
  arguments onto it.
- Register the standard encodeable types (Argument) on the value decoder
  so NodeSet2 method argument lists actually decode (was silently empty).
- Disambiguate a by-ref output argument whose name matches an input.
- Skip synthesizing method types for base-namespace (inherited) methods
  and for standalone method-type declarations.

Deprecated 1.02 nodes remain usable and all 1.02 tests pass unchanged
(only mechanical Ua.DataTypeIds qualification where the new combined
DataTypeIds shadows Core).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@marcschier

Copy link
Copy Markdown
Collaborator Author

Superseded by the 14-PR stack #4159 plus three follow-up draft PRs. Closing after a line-level audit confirmed full coverage.

How this was verified

A raw diff between this branch and the stack tip is misleading — they have different merge-bases with master (cfd15f08e vs ae241b662), so master churn pollutes the comparison. Instead:

  1. Compared blob hashes for all 374 files this PR touches → 229 identical, 137 differing, 8 absent.
  2. For every line this PR adds, tested membership against an index of every distinct line in the whole target tree (~636,000 lines), not just the same file. That removes false positives from the file splits, moves and renames the stack performed.

Starting gap: 742 lines across 84 files (~99.3 % already covered). After the three follow-up PRs: 303 lines, all classified below.

Where the content went

Area PRs
Core types, converter, source generation #4128, #4131, #4132, #4134
NodeManager lifecycle, subscription transfer #4147, #4150
xRegistry client, WoT Connectivity model #4130, #4139
Bindings core, executors, registry #4140, #4144, #4146
Client, materialization, samples #4142, #4154, #4156
Monitored item retirement #4173
WoT → xRegistry bridge #4171
Restored test coverage and docs #4172

Residual differences — all intentional

The remaining 303 lines are places where the stack is newer or better, plus deliberate skips:

  • Superseded implementations — the 7-site pure-resolver approach in NodeStateGenerator replaces the ModelDesignValidator / MethodDesignArgumentResolver code-name assignment; WotNodeSetConverter.Mapping.cs was split into .NodeSet.cs + .WoT.cs; IAtomicFileReplace was retired in favour of IFileSystem.Replace; WotResolver's cycle guard is now HashSet<ResolutionKey> + an active stack with diagnostics rather than a plain HashSet<string>; DI moved from TryAddEnumerable to explicit interface registrations; ?? [] became .Empty (a collection expression binds to ImmutableDictionary<K,V> only on net10.0).
  • Obsolete tests — the old ModelSourceGeneratorHoist / EmbeddedText generator harness, string-spliced malformed-extension JSON, and a brittle private-reflection test reaching into SubscriptionManager.ConditionRefreshTask and m_conditionRefreshQueue.
  • Namespace-qualification differences — e.g. Registry.IWotRegistryService here vs a using plus the unqualified name in Restore WoT asset registry bridge #4171. Same code, different spelling.

Fixes carried across

Two improvements from this PR that the split had dropped were deliberately preserved rather than lost:

Validated on a tree with all three follow-up PRs merged: Opc.Ua.Server.Tests 4048 passed / 0 failed, Opc.Ua.WotCon.Tests 986 passed / 0 failed.

@marcschier marcschier closed this Aug 3, 2026
marcschier added a commit that referenced this pull request Aug 3, 2026
…n context (#4163)

# Description

`RequestManager` tracked whether the calling flow was serving a Client
request in a `private readonly AsyncLocal<bool> m_inServiceDispatch`. It
was set by a `StandardServer.ProcessRequestAsync` override and read by
`NodeManagerLifecycle`, so that a lifecycle operation (`AddAsync` /
`ReloadAsync` / `RemoveAsync`) started from inside a request fails fast
with `InvalidOperationException` instead of draining — and therefore
waiting for — its own request.

Two problems with that mechanism, raised in review of #4093:

1. An `AsyncLocal<T>` written inside an `async` method never flows back
to the caller that awaited it, so every scope had to chain to the
previous value by hand and restore it on dispose. Subtle, and easy to
get wrong when a new entry point is added.
2. Ambient state is invisible at the call site, so the lifetime of the
mark could not be reasoned about from the code that depended on it —
especially once work is handed to a background task.

`RequestManager` also had no business carrying ambient state on behalf
of a different subsystem; its job is tracking requests for the drain.

## What changed

The guard is now an **exact identity lookup** against the request
registry `RequestManager` already maintains, and the operation is
threaded **explicitly**:

* **`RequestManager`** — `internal bool
IsExecutingRequest(IOperationContext?)` performs a reference-identity
lookup against `m_requests` under the existing `m_requestsLock`.
`m_inServiceDispatch`, `EnterServiceDispatchScope()`, the nested
`ServiceDispatchScope` class and the flag-based `IsExecutingRequest`
property are gone.
* **`StandardServer`** — the `ProcessRequestAsync` override is removed;
it existed only to open the dispatch scope.
* **`INodeManagerLifecycle`** — every member (`AddAsync` ×2 factory
kinds, `ReloadAsync` ×2, `RemoveAsync`) now takes the caller's
`IOperationContext?` directly, ahead of `CancellationToken ct =
default`. No overloads and no parallel signature are kept: the interface
is new in 2.0 and outside the 1.5.378 compatibility rule, so call sites
state their context explicitly or pass `null`.
* **`HostedNodeManagerLifecycle`** and
**`RuntimeNodeSetLifecycleExtensions`** thread the argument through;
`AddRuntimeNodeSetAsync` / `ReloadRuntimeNodeSetAsync` remain single
methods.
* **`SystemContextOperationExtensions.GetOperationContext(this
ISystemContext)`** (new, `Opc.Ua.Core`) lets a NodeManager or Method
callback hand over the operation it already received without
downcasting. It switches on `SessionSystemContext` and `SystemContext`,
the two independent roots every context derives from. No new interface
is introduced: casting the context to `IOperationContext` would not do,
because a context implements that by delegating to the operation it was
created for, so the cast yields the context rather than the operation.
* **`NodeManagerLifecycle.EnsureNotRequestCallback`** no longer consults
`ServerState`. The previous `CurrentState == Running` predicate could
let a genuinely re-entrant call slip past while the server was shutting
down; the request registry answers exactly in every state.

A NodeManager or Method callback now reads:

```csharp
await m_lifecycle.ReloadAsync(m_registration, replacement, context.GetOperationContext(), ct);
```

and a control-plane caller (hosted service, DI consumer) passes `null`.

## Behaviour

Unchanged for the case the guard exists to catch: a lifecycle call made
on behalf of an executing request is still rejected up front with the
same `InvalidOperationException` and the same message.

Two deliberate differences:

* **Improvement** — an internal `OperationContext` that was never
enrolled as a Client request is now correctly allowed through. The
boolean flag could not distinguish it from a real request.
* **Accepted trade-off** — a caller inside a request that passes no
operation is no longer detected at entry and falls back to the
pre-existing bounded `RequestManager.RequestDrainTimeout`, which already
covered any request that bypassed the service pipeline. Automatic
detection for an uncooperative caller inherently requires ambient state,
which is exactly what this change removes. Documented in
`docs/NodeManagers.md`.

## Alternatives considered and rejected

| Alternative | Why rejected |
| --- | --- |
| Delete the guard, rely only on `RequestDrainTimeout` | Turns a clear
immediate error into a multi-minute hang. |
| Infer the caller from the `CancellationToken` it passes | Works only
for the exact request token, breaks on linked tokens; ambient magic of a
different kind. |
| In-flight callback counter on `MasterNodeManager` | False positives:
rejects legitimate control-plane calls whenever any request is
concurrently dispatching. |
| Defer the drain to a background task so re-entrancy cannot deadlock |
The drains are woven into transactional commit / rollback and binding
reconciliation; deferring them breaks the transactional guarantees. |
| `Activity.Current` / `ThreadLocal` | The same ambient magic the issue
asks to remove. |

## Testing

* `RequestManagerTests` — the three `EnterServiceDispatchScope` tests
are replaced by coverage for `IsExecutingRequest(context)`: null,
never-registered, only-while-scope-open, visible across an `await` and a
background `Task.Run`, and two concurrently executing requests not
confusing each other.
* `NodeManagerLifecycleTests` — rejection from an executing request for
Add / Reload / Remove without invoking the factory, acceptance for a
context that is not an executing request, acceptance once the request
has completed, and the full callback shape
(`ServerSystemContext.Copy(operation)` → `GetOperationContext()` →
rejected).
* `HostedNodeManagerLifecycleTests` — forwarding tests for the
caller-context argument on all five members.
*
`tests/Opc.Ua.Core.Tests/Stack/State/SystemContextOperationExtensionsTests.cs`
(new) — `GetOperationContext` across both context roots and the
null-argument guard.

## Related Issues

- Fixes #4149

## Checklist

- [x] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [x] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 14402d11-e540-4faa-a5cd-94881bd7ce85
marcschier added a commit that referenced this pull request Aug 5, 2026
…ion (#4156)

Adds the runnable WoT Connectivity samples and completes the
documentation. This is **PR 16 of a
stack** splitting #4093, and the last one.

## Samples

Three projects under `samples/WotCon/`:

* **FlatTagServer** — a plain OPC UA server exposing flat tags, used as
an aggregation source.
* **AggregationServer** — registers Thing Descriptions with the WoT
registry and materializes two
flat sources into a runtime-loaded DI/Machinery/Pumps Pump model. It
also ships a small in-memory
protocol binding, which is the worked example the binding contributor
guide walks through.
* **AggregationClient** — drives the aggregated model: commands,
`Refresh`, monitoring, and
  generation replacement.

## Documentation

* New `docs/WotBindings.md` — the bindings that ship today
(planner/executor architecture, bundled
vs separate packages, operation coverage, target mapping, lazy channels,
generation lifetime) plus
the contributor guide for adding your own, with diagnostics, tests,
packaging, TFM, trimming and
  NativeAOT guidance.
* Expanded `docs/WoTConnectivity.md` covering the registry,
dependency-closure materialization and
  runtime NodeSet projection.
* `docs/RuntimeNodeSets.md` gains the shadow-reload section and
`docs/XRegistry.md` the shared
byte-store section. Both document APIs introduced earlier in the stack;
they are collected here
  because this is the documentation PR.
* `docs/README.md` links the new pages.

## Solution registration

Registers the three samples and `Opc.Ua.WotCon.Samples.Tests` in
`UA.slnx` so CI actually builds
and runs them.

## Stack position

Sits on top of **#4154** (materialization) and additionally merges
**#4142** (registry client) and
**#4144** (protocol executors), because the samples exercise the whole
stack — `AggregationServer`
needs the OPC UA executor and the client sample needs the registry
client. Its diff therefore shows
their content until they land.

It also carries `WotRegistryProjectionLiveTests.cs`, which #4154
deliberately left out: those tests
need both the materialization runtime *and* the registry client, so this
is the first branch where
they compile.

## Validation

* All three samples build clean on every target framework.
* `Opc.Ua.WotCon.Tests`: **830 passed**, 0 failed (769 from #4154 plus
61 from the live projection
  tests).
* `Opc.Ua.WotCon.Samples.Tests`: 6 of 7 pass locally; the remaining one
fails with
`CryptographicException: The system cannot find the path specified`, a
known certificate-store
problem on this machine that was previously reproduced against a clean
baseline worktree and is
  not related to this change. CI will confirm.
* 0 warnings, 0 errors.

One fix was needed while bringing the sample across: its memory binding
called `ResolveCodec` with
the pre-`out WotPayloadDescriptor` signature. The API gained the payload
out-parameter and a
`bool` result during review of the binding PRs, so the sample now
follows the same
`if (!ResolveCodec(...)) return Unsupported(...)` pattern as the shipped
planners.

Integration PR: #4093.
marcschier added a commit that referenced this pull request Aug 5, 2026
…pace (#4154)

PR 14 of the stacked series splitting the large WoT Connectivity
integration PR #4093.

This PR adds the server-side materialization runtime that turns WoT
Thing Description resources from the xRegistry snapshot into OPC UA
address-space projections. The coordinator builds the dependency closure
for each resource, plans binding metadata, activates binding channels,
and publishes the resulting projection through the registry NodeManager
so refresh, retire, and reload operations remain observable and
deterministic.

Projection intentionally goes through the NodeManager lifecycle reload
API instead of mutating live nodes directly. That keeps complex type
reload, runtime NodeSet publication, shadow reload, immediate reload,
and retirement on the same lifecycle path as other dynamic server
address-space changes.

This branch has a triple dependency. It is based on #4146 (registry),
and additionally needs #4147 (lifecycle reload API) and #4128
(2-argument TryGetStructure). Both #4147 and #4128 are merged into this
branch, so this PR's diff will show their content until those
dependencies land.

Validation performed:
- dotnet build src\Opc.Ua.WotCon.Server\Opc.Ua.WotCon.Server.csproj -c
Release -f net10.0 -v:m
- dotnet build tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c
Release -p:CustomTestTarget=net10.0 -v:m
- dotnet test tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c
Release -p:CustomTestTarget=net10.0 --no-build
- dotnet build tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c
Release -p:CustomTestTarget=net48 -v:m
- dotnet test tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c
Release -p:CustomTestTarget=net48 --no-build
- dotnet build tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj -c
Release -p:CustomTestTarget=net10.0 -v:m
- dotnet test tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj -c
Release -p:CustomTestTarget=net10.0 --no-build (one known inherited
failure: LiveNodeManagerAddRefreshesTheClientNamespaceTable)
marcschier added a commit that referenced this pull request Aug 5, 2026
PR 15 of a stacked split of integration PR #4093 into independently
reviewable pieces.

This branch is stacked on #4139 (`marcschier/wot-10-wotcon-model`) and
additionally has #4128 (`marcschier/wot-01-core-types`) and #4130
(`marcschier/wot-09-xregistry-client`) merged in so this client slice
can build.

Those extra dependencies are intentional:
- Without #4130: `WotRegistryClient.cs:83` — `error CS1729:
'XRegistryClient' does not contain a constructor that takes 4
arguments`.
- Without #4128: `WotRegistryClient.cs:346,375` and
`WotRegistryResourceClient.cs:144` — `error CS1501: No overload for
method 'TryGetStructure' takes 2 arguments`.

The extra diff from #4128 and #4130 disappears once those PRs land.

This PR adds the WoT Connectivity registry client surface for browsing
and reading registry groups, resources and versions, uploading documents
through FileTransfer, invoking registry methods, and registering the
client through DI/builder helpers.
marcschier added a commit that referenced this pull request Aug 5, 2026
Adds the WoT Connectivity registry service and stores for Thing
Description and Thing Model documents.

This is PR 13 of the stacked split of integration PR #4093 and is
stacked on #4140 (`marcschier/wot-11-bindings-core`). The
materialization runtime that consumes this registry follows separately.

The registry has no dependency on the binding runtime or the NodeManager
lifecycle; the stacking is only because this change shares the WoT
Connectivity server project file with the preceding PRs.

Validation:
- `dotnet build src\Opc.Ua.WotCon.Server\Opc.Ua.WotCon.Server.csproj -c
Release -f net10.0 -v:m`
- `dotnet build tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c
Release -p:CustomTestTarget=net10.0 -v:m`
- `dotnet test tests\Opc.Ua.WotCon.Tests\Opc.Ua.WotCon.Tests.csproj -c
Release -p:CustomTestTarget=net10.0 --no-build -v:q`
- `dotnet build src\Opc.Ua.WotCon.Server\Opc.Ua.WotCon.Server.csproj -c
Release -v:m`
marcschier added a commit that referenced this pull request Aug 5, 2026
## Summary
- Adds the concrete WoT binding transport executors for HTTP, Modbus
TCP, OPC UA and MQTT.
- Includes executor/channel tests and in-process HTTP, Modbus, MQTT and
OPC UA harness coverage.
- Leaves CoAP, BACnet, PROFINET and LoRaWAN planner-only because this
stack split intentionally lands validation/planning before executable
transports.

## Stack context
This is PR 12 of the stacked split of integration PR #4093. It is
stacked on #4140, where the protocol-agnostic planner layer landed
separately, so this PR is limited to the concrete transport
implementations.

## Validation
- `dotnet build
tests\Opc.Ua.WotCon.Bindings.Tests\Opc.Ua.WotCon.Bindings.Tests.csproj
-c Release -p:CustomTestTarget=net10.0 -v:m`
- `dotnet test
tests\Opc.Ua.WotCon.Bindings.Tests\Opc.Ua.WotCon.Bindings.Tests.csproj
-c Release -p:CustomTestTarget=net10.0 --no-build` (512 passed)
- `dotnet build src\Opc.Ua.WotCon.Bindings\Opc.Ua.WotCon.Bindings.csproj
-c Release -v:m`
- `dotnet build
src\Opc.Ua.WotCon.Bindings.Mqtt\Opc.Ua.WotCon.Bindings.Mqtt.csproj -c
Release -v:m`
marcschier added a commit that referenced this pull request Aug 5, 2026
## Summary

PR 11 of the stacked split for integration PR #4093. This adds the
protocol-agnostic core of `Opc.Ua.WotCon.Bindings`: binder, planner,
executor and channel-factory contracts; payload codec and credential
provider seams; the binding plan model; polling subscriptions for forms
without native observation; and DI/builder registration helpers.

This PR is stacked on #4139, which itself depends on #4132 and #4134.

## Scope

The included planners validate forms and produce binding plans without
transport I/O:

- HTTP planner and validation only; the concrete HTTP executor follows
in the next PR.
- MQTT planner and validation only; the separate MQTT executor package
follows in the next PR.
- Modbus TCP planner and shared Modbus type/limit definitions only; the
Modbus executor and TCP client follow in the next PR.
- OPC UA planner and validation only; the concrete OPC UA executor
follows in the next PR.
- CoAP, BACnet, PROFINET and LoRaWAN are planner/validation-only in this
split.

Keeping concrete transport executors out of this PR makes the
abstractions and planning layer reviewable without a network stack.

## Validation

- `dotnet build
tests\Opc.Ua.WotCon.Bindings.Tests\Opc.Ua.WotCon.Bindings.Tests.csproj
-c Release -p:CustomTestTarget=net10.0 -v:m`
- `dotnet test
tests\Opc.Ua.WotCon.Bindings.Tests\Opc.Ua.WotCon.Bindings.Tests.csproj
-c Release -p:CustomTestTarget=net10.0 --no-build`
- `dotnet build src\Opc.Ua.WotCon.Bindings\Opc.Ua.WotCon.Bindings.csproj
-c Release -v:m`
marcschier added a commit that referenced this pull request Aug 5, 2026
This is PR 10 of the stacked split of integration PR #4093 into
independently reviewable pieces.

Stack/dependency shape:

- This branch is stacked on #4134 (`marcschier/wot-04-generator-wot`).
- It also requires #4132 (`marcschier/wot-02-sourcegen`), which is
merged into this branch so the model can build.

The #4132 dependency is functional, not cosmetic. Without #4132,
building the WoT Connectivity model on #4134 alone fails with duplicate
generated declarations, for example:

```text
CS0102: The type 'Methods' already contains a definition for 'CreateAssetMethodType'
```

That occurs because the combined WoT-Con NodeSet ships the 1.02
`CreateAssetMethodType` node explicitly; without #4132's
NodeSet-to-ModelDesign fix, the generator synthesizes a second
declaration with the same name.

Once #4132 and #4134 land, the extra diff from those dependencies
disappears and this PR reduces to the three WoT Connectivity model
files:

- `src\Opc.Ua.WotCon\Design\Opc.Ua.WotCon.NodeSet2.xml`
- `src\Opc.Ua.WotCon\Design\Opc.Ua.WotCon.NodeSet2.csv`
- `src\Opc.Ua.WotCon\Opc.Ua.WotCon.csproj`

The generated C# is produced from the pinned NodeSet2 at build time. I
verified it builds from clean after deleting the WotCon `bin`/`obj`, the
source-generation tool `bin`/`obj` directories, and shutting down dotnet
build servers.
marcschier added a commit that referenced this pull request Aug 5, 2026
This extracts the xRegistry client generalisation from #4093 as PR 9 of
the stacked split into independently reviewable pieces.

The change lets the xRegistry client base accept an explicit registry
root NodeId while preserving the existing well-known-root default.
GenericXRegistryClient exposes the same explicit-root path so callers
can drive registries whose root Object is discovered separately, and the
tests cover the supplied-root and fallback behavior across the lifecycle
helpers.

This PR is independent of the other stack PRs and contains no WoT
registry implementation or Opc.Ua.WotCon.* references. A later PR adds
the WoT registry client on top of this general xRegistry client surface.
marcschier added a commit that referenced this pull request Aug 5, 2026
…4150)

This is PR 7 of the stacked series splitting integration PR #4093,
stacked on #4147 (`marcschier/wot-05-lifecycle`).

This PR makes subscription transfer between sessions transactional:
subscriptions are prepared before any ownership move, publish-queue
transfer claims block stale source-session publishing, and rollback
restores monitored-item resend-data trigger state.

`ServerInternalData` also gains asynchronous disposal so the
asynchronously disposable subscription manager is shut down without
blocking, with sync and async dispose paths guarded so repeated disposal
is a no-op.

It also brings the transfer-focused coverage for subscription transfer,
publish-queue transfer claims, and `ServerInternalData`
async/double-disposal behavior.

The immediate monitored-item retirement mechanism present in #4093 is
deliberately excluded per maintainer decision. `IRetirableMonitoredItem`
and `INodeManagerMonitoredItemRetirementTracker` do not appear in this
PR.
marcschier added a commit that referenced this pull request Aug 5, 2026
## Summary

Adds PR 5 of the stack splitting #4093 into independently reviewable
pieces, targeting `master` directly.

This PR extends the NodeManager lifecycle with live shadow reload
support and RuntimeNodeSet lifecycle helpers. `ShadowReloadAsync` stages
a replacement generation and routes new service requests to it while
monitored items already owned by the retired generation continue there
until subscriptions drain.

`ImmediateReloadAsync` keeps the immediate policy but implements it on
the existing detach machinery at the maintainer's request: affected
non-durable monitored items are detached/marked deleted with
`IDetachableMonitoredItem`, queue `BadNodeIdUnknown`, and are not
migrated to the replacement generation.

The prior NodeManager generation is disposed before
`ImmediateReloadAsync` returns in the covered lifecycle path. The
bespoke permanent retirement-error path is intentionally removed, so
follow-up operations use the existing detached-item semantics rather
than a separate retirement state.

## Validation

- `dotnet build src\Opc.Ua.Server\Opc.Ua.Server.csproj -c Release -f
net10.0 -v:m`
- `dotnet build tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj -c
Release -p:CustomTestTarget=net10.0 -v:m`
- `dotnet test tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj -c
Release -p:CustomTestTarget=net10.0 --no-build` — 3914 passed, 9
skipped, 0 failed
- `dotnet build tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj -c
Release -p:CustomTestTarget=net48 -v:m`
- `dotnet test tests\Opc.Ua.Server.Tests\Opc.Ua.Server.Tests.csproj -c
Release -p:CustomTestTarget=net48 --no-build` — full run hit local
durable queue file-lock flake; the failed test passed in isolation. Full
run otherwise reported 3913 passed, 9 skipped, 1 failed.
marcschier added a commit that referenced this pull request Aug 5, 2026
…files (#4134)

## Summary
- Adds WoT Thing Description and Thing Model AdditionalFiles support to
Opc.Ua.SourceGeneration for .tm.json, .td.json, .tm.jsonld, .td.jsonld,
and explicitly opted-in .jsonld inputs.
- Converts WoT inputs to an in-memory UANodeSet and reuses the existing
NodeSet generation pipeline for constants, node states, and proxies.
- Reports parsing, conversion, and virtual path collision failures as
MODELGEN030 through MODELGEN034 diagnostics instead of generator
exceptions.

## Stack context
This is PR 4 of the stacked split of integration PR #4093. It is stacked
on #4131 (`marcschier/wot-03-converter`) and should be reviewed and
merged after that PR.

The source-generation entry-point project is included whole in this PR
because its final state is irreducibly WoT-coupled. The genuinely
generic generator work lives in the separate PR 2 branch.

## Notes
Roslyn source generators must run synchronously. This PR drives WoT
conversion through the converter's synchronous entry point and does not
use the asynchronous resolver contracts from generator code.

## Validation
- `dotnet build
tools\Opc.Ua.SourceGeneration\Opc.Ua.SourceGeneration.csproj -c Release
-v:m`
- `dotnet build
tests\Opc.Ua.SourceGeneration.Tests\Opc.Ua.SourceGeneration.Tests.csproj
-c Release -p:CustomTestTarget=net10.0 -v:m`
- `dotnet test
tests\Opc.Ua.SourceGeneration.Tests\Opc.Ua.SourceGeneration.Tests.csproj
-c Release -p:CustomTestTarget=net10.0 --no-build`
- `dotnet build
tests\Opc.Ua.SourceGeneration.Tests\Opc.Ua.SourceGeneration.Tests.csproj
-c Release -p:CustomTestTarget=net48 -v:m`
- `dotnet test
tests\Opc.Ua.SourceGeneration.Tests\Opc.Ua.SourceGeneration.Tests.csproj
-c Release -p:CustomTestTarget=net48 --no-build`
marcschier added a commit that referenced this pull request Aug 5, 2026
…rated node state (#4132)

## Summary
- Improves NodeSet to ModelDesign conversion for combined and
multi-namespace NodeSets.
- Fixes generated method declarations, argument resolution, object type
proxies, fluent builders, generated node state, shared generation
helpers, and validation.
- Regenerates ISA95 NodeIds as generator output from these changes, not
as a hand edit.

## Stack context
This is PR 2 of a stacked split of integration PR #4093 into
independently reviewable pieces. This PR is independent of the other
stack PRs and contains no WoT content.

## Validation
- `dotnet build
tests\Opc.Ua.SourceGeneration.Core.Tests\Opc.Ua.SourceGeneration.Core.Tests.csproj
-c Release -p:CustomTestTarget=net10.0 -v:m`
- `dotnet test
tests\Opc.Ua.SourceGeneration.Core.Tests\Opc.Ua.SourceGeneration.Core.Tests.csproj
-c Release -p:CustomTestTarget=net10.0 --no-build`
- `dotnet build
tests\Opc.Ua.SourceGeneration.Core.Tests\Opc.Ua.SourceGeneration.Core.Tests.csproj
-c Release -p:CustomTestTarget=net48 -v:m`
- `dotnet test
tests\Opc.Ua.SourceGeneration.Core.Tests\Opc.Ua.SourceGeneration.Core.Tests.csproj
-c Release -p:CustomTestTarget=net48 --no-build`
- `dotnet build src\Opc.Ua.ISA95\Opc.Ua.ISA95.csproj -c Release -f
net10.0 -v:m`
- Confirmed ISA95 build leaves
`src\Opc.Ua.ISA95\Design\Common\Opc.ISA95.NodeIds.csv` with no unstaged
regeneration diff.
marcschier added a commit that referenced this pull request Aug 5, 2026
…4131)

Adds a dependency-light, AOT-safe lexical model for Thing Description
and Thing Model documents to Opc.Ua.Types, together with lossless
converters between WoT documents and NodeSet2 and canonical NodeSet
comparison for round-trip checks.

This is PR 3 of a stacked split of #4093 into independently reviewable
pieces. It is self-contained, targets master directly, and depends on no
other PR in the stack.

The change is a pure library in Opc.Ua.Types with no server/client
involvement and no network I/O of its own. Unknown JSON-LD members are
preserved as digest-protected RFC 6901 residue entries, native NodeSet
projection is emitted according to the preservation mode, and resolver
contracts expose asynchronous bounded resolution with structured
diagnostics.

This PR is still large (~8,000 lines). Suggested review passes:

1. Lexical model and residue preservation.
2. Converter and native projection.
3. Resolver contracts and NodeSet comparison.
marcschier added a commit that referenced this pull request Aug 5, 2026
…ation, Server and xRegistry work it builds on (#4128)

## Summary

Adds OPC UA WoT Connectivity 1.1 to the stack, together with the Types,
source generation, Server and xRegistry work it is built on. This branch
is the merge point for the fourteen-PR stack that replaced integration
PR #4093; every constituent PR was reviewed and approved separately and
is listed below.

414 files changed, +108,936 / -3,199. Roughly 46k added lines of product
code, 57k of tests, plus samples, tools and documentation.

## Types

- Complete `ExtensionObject` raw-body decoding for binary, XML and JSON
bodies by resolving the concrete type through the message context's
encodeable factory, and simplify the `Variant` structure helpers by
delegating to it (#4128).
- Add `IAtomicFileReplace` as an optional `IFileSystem` capability so
existing external implementations keep working, with atomic publish for
`LocalFileSystem` via `File.Replace`/`File.Move` and for
`VirtualFileSystem` by re-keying the in-memory entry (#4128).
- Add lossless conversion between WoT documents and NodeSet2
(`Opc.Ua.Wot.WotNodeSetConverter`). A byte-exact `uav:nodeSet` envelope
is preserved when requested, the structured `uav:nodes` projection is
used when the readable vocabulary is incomplete, and NodeSet2 is
otherwise synthesized from readable WoT terms. Unmapped JSON members
survive a round trip as pointer-addressed residue (#4131).

## Source generation

- Improve the NodeSet to ModelDesign conversion and the generated node
state (#4132).
- Generate OPC UA models directly from WoT Thing Description files
(#4134).

## Server

- Add live NodeManager shadow and immediate reload. A reload
materializes a new generation beside the active one and switches
atomically; the superseded generation is retired gracefully so existing
MonitoredItems keep being served until they drain, or immediately when
the caller asks for it. Includes the request admission and drain
machinery that lets an orderly shutdown wait for admitted requests
instead of tearing down underneath them (#4147).
- Make subscription transfer between sessions transactional, so a failed
transfer leaves neither session holding a partially moved subscription
(#4150).

## xRegistry

- Allow clients to use explicit registry roots rather than assuming a
well-known location (#4130).

## WoT Connectivity

- Add the WoT Connectivity 1.1 information model: a registry-first
revision layered on the abstract xRegistry base model, incorporating the
complete published OPC 10100-1 v1.02 surface as deprecated nodes in the
same namespace so existing 1.02 clients keep working (#4139).
- Add the protocol binding abstractions and planners that compile WoT
forms into executable plans (#4140).
- Add the HTTP, Modbus, OPC UA and MQTT binding executors (#4144).
- Add the registry and its stores, including document versioning,
validation, dependency resolution and refresh (#4146).
- Add the registry client (#4142).
- Materialize WoT Thing Descriptions into the server address space,
deriving types from Thing Models and instances from Thing Descriptions
(#4154).
- Restore the WoT asset registry bridge so the deprecated 1.02 asset
surface is backed by the registry (#4171).
- Add the WoT Connectivity samples and complete the documentation
(#4156), and restore the remaining coverage and docs (#4172).

## New projects

`src/Opc.Ua.WotCon.Bindings`, `src/Opc.Ua.WotCon.Bindings.Mqtt`,
`tests/Opc.Ua.WotCon.Bindings.Tests`,
`tests/Opc.Ua.WotCon.Samples.Tests`, and the `samples/WotCon`
AggregationServer, AggregationClient and FlatTagServer samples.

## Constituent pull requests

Merged into this branch top-down, each independently approved:

| PR | Title |
| --- | --- |
| #4128 | [Types] Complete ExtensionObject decoding and add an atomic
file replace capability |
| #4131 | [Types] Add lossless conversion between WoT documents and
NodeSet2 |
| #4132 | [SourceGeneration] Improve NodeSet to ModelDesign conversion
and generated node state |
| #4134 | [SourceGeneration] Generate OPC UA models from WoT Thing
Description files |
| #4147 | [Server] Add live NodeManager shadow and immediate reload |
| #4150 | [Server] Make subscription transfer between sessions
transactional |
| #4130 | [XRegistry] Allow clients to use explicit registry roots |
| #4139 | [WotCon] Add the WoT Connectivity 1.1 information model |
| #4140 | [WotCon] Add the WoT protocol binding abstractions and
planners |
| #4144 | [WotCon] Add the HTTP, Modbus, OPC UA and MQTT binding
executors |
| #4146 | [WotCon] Add the WoT Connectivity registry and its stores |
| #4142 | [WotCon] Add the WoT Connectivity registry client |
| #4154 | [WotCon] Materialize WoT Thing Descriptions into the server
address space |
| #4156 | [WotCon] Add the WoT Connectivity samples and complete the
documentation |
| #4171 | Restore WoT asset registry bridge |
| #4172 | Restore WoT coverage and docs |

Supersedes #4093.

## Follow-up

The WoT Connectivity and WoT Binding drafts have moved to 1.1-draft2
since this work was authored. Aligning with that revision - the removed
group vocabulary, the new projection/View construct, the remaining model
vocabulary terms and the two new portable-identity validation rules - is
tracked separately and will follow in its own pull request.

## Validation

Built for every target framework with zero warnings and zero errors, and
validated at the tip of the stack with `Opc.Ua.WotCon.Tests` at 999
passed / 0 failed and `Opc.Ua.Server.Tests` at 4024 passed / 0 failed.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 9e6a5abf-3299-4cd1-9855-010fedbf0ad8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants